////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2010 Gabriel Montagné Láscaris-Comneno and Alberto
//  Brealey-Guzmán.
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package com.rojored.remote
{

import com.rojored.remote.errors.NoIdPropertyError;
import flash.errors.IllegalOperationError;
import flash.utils.describeType;
import flash.utils.getQualifiedClassName;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.rpc.AbstractOperation;
import mx.rpc.AsyncToken;
import mx.rpc.remoting.RemoteObject;
import mx.utils.ObjectUtil;

/**
 *  Handles all the DS persistance operations
 */
public class DSDelegate
{

    //--------------------------------------------------------------------------
    //
    //  Class constants
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private static const LOG:ILogger =
        Log.getLogger(getQualifiedClassName(DSDelegate).replace(/\W+/g, "."));

    /**
     *  Remote operation name for save
     */
    private static const SAVE:String = "save";

    /**
     *  Remote operation name for index
     */
    private static const INDEX:String = "index";

    /**
     *  @private
     *  Remote operation name for get.
     */
    private static const GET:String = "get";

    /**
     *  Remote operation name for destroy
     */
    private static const DESTROY:String = "destroy";

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor
     */
    public function DSDelegate()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    //  service
    //-------------------------------------

    /**
     *  Remote service reference
     */
    public var service:RemoteObject;

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Destroy an item in the DS
     */
    public function destroy(item:*):AsyncToken
    {
        return runServiceMethod(DESTROY, item.id);
    }

    /**
     *  Request an index.
     */
    public function index(parameters:Object = null):AsyncToken
    {
        return runServiceMethod(INDEX, parameters);
    }

    /**
     *  Save or create an item.
     */
    public function save(item:*):AsyncToken
    {
        return runServiceMethod(SAVE, toAttributes(item));
    }

    /**
     *  Get, retrieve a saved item
     */
    public function get(id:String):AsyncToken
    {
        return runServiceMethod(GET, id);
    }

    /**
     *  @private
     *  Create and perform an operation
     */
    protected function runServiceMethod(operationName:String,
                                        ... args):AsyncToken
    {
        if (!service)
            throw new Error("Service not wired for " + this);

        LOG.info("runServiceMethod {0} {1}", operationName, (args ?
            args.join("") : ""));

        var operation:AbstractOperation = service.getOperation(operationName);
        return operation.send.apply(operation, args);
    }

    /**
     *  @private
     *  Create a property hash for a given object.
     */
    public function toAttributes(item:*):Object
    {
        var attributes:Object = {};
        var classInfo:Object = ObjectUtil.getClassInfo(item);
        var alias:String = classInfo.alias;
        var properties:Array = classInfo.properties;
        var propertyName:String;
        var metadataInfo:Object;
        var propertyValue:*
        var isMetadataPresent:Boolean;
        for each (var propertyInfo:Object in properties)
        {
            propertyName = propertyInfo.localName;
            metadataInfo = (classInfo.metadata &&
                classInfo.metadata[propertyName] ) || {};

            switch (true)
            {
                case "Transient" in  metadataInfo:
                case "Convenience" in metadataInfo:
                {
                    break;
                }

                case "Reference" in metadataInfo:
                {
                    propertyValue = item[propertyName];
                    if (propertyValue === null)
                        break;

                    switch (true)
                    {
                        case "id" in propertyValue:
                        {
                            attributes[propertyName] = propertyValue.id;
                            break;
                        }

                        case propertyValue is Array:
                        {
                            var references:Array = [];
                            var referencedItems:Array = propertyValue as Array;
                            var n:int = referencedItems.length;
                            var referencedItem:Object;
                            for (var i:int = 0; i < n; i++)
                            {
                                referencedItem = referencedItems[i];
                                if ("id" in referencedItem)
                                {
                                    references.push(referencedItem.id);
                                }
                                else
                                {
                                    throw new NoIdPropertyError(
                                        "Nested referenced item " +
                                            referencedItem +
                                            "has no property 'id'"
                                            );
                                }
                            }

                            if (references.length)
                                attributes[propertyName] = references;

                            break;
                        }

                        default:
                        {
                            throw new NoIdPropertyError("Referenced item " +
                                referencedItem + "has no property 'id'");
                            break;
                        }
                    }
                    break;
                }

                default:
                {
                    propertyValue = item[propertyName];
                    if (propertyValue === null)
                        break;

                    attributes[propertyName] = propertyValue;
                    break;
                }
            }
        }
        return attributes;
    }
}
}
